package com.rankeiot.core.excel;

import cn.hutool.core.text.csv.CsvUtil;
import cn.hutool.core.text.csv.CsvWriter;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.rankeiot.core.anno.Excel;
import com.rankeiot.core.data.Item;
import com.rankeiot.core.translator.FormatTranslator;
import com.rankeiot.core.translator.Translator;
import com.rankeiot.core.util.CollectionUtil;
import com.rankeiot.core.util.StringUtil;

import lombok.extern.java.Log;
import org.sagacity.sqltoy.callback.StreamResultHandler;
import org.sagacity.sqltoy.utils.BeanUtil;

import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.lang.reflect.Field;
import java.util.*;

/**
 * 流式导出CSV数据
 */
@Log
public class CSVStreamExporter implements StreamResultHandler {

    private final Map<String, Translator> translators;

    private Map<Integer, Translator> colTranslators;

    private String[] columnsLabels;

    private boolean isListExport = false;

    //获得导出字段及表头
    private List<Item> titles = new ArrayList<>();

    private boolean dataIsMap = false;
    private long startTime;
    private long total;
    private boolean firstRow = true;
    private OutputStream out;
    private CsvWriter cw;

//    public CSVStreamExporter(HttpServletResponse response, String fileName) {
//        this(response, new HashMap<>(),fileName);
//    }
//
//    public CSVStreamExporter(HttpServletResponse response, Map<String, Translator> translators,String fileName) {
//        response.setHeader("Content-Encoding", "gzip");
//        response.setCharacterEncoding("UTF-8");
//        response.setContentType("application/vnd.ms-excel");
//        try {
//            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8")+".csv");
//        } catch (UnsupportedEncodingException e) {
//            e.printStackTrace();
//        }
//        try {
//            this.translators = translators;
//            this.out = new GZIPOutputStream(response.getOutputStream());
//            cw = CsvUtil.getWriter(new OutputStreamWriter(out));
//        } catch (IOException e) {
//            throw new RuntimeException(e);
//        }
//    }

    public CSVStreamExporter(OutputStream out) {
        this(out, new HashMap<>());
    }

    public CSVStreamExporter(OutputStream out, Map<String, Translator> translators) {
        this.out = out;
        this.translators = translators;
        cw = CsvUtil.getWriter(new OutputStreamWriter(out));
    }

    public void start(String[] columnsLabels, String[] columnsTypes) {
        startTime = System.currentTimeMillis();
        this.columnsLabels = columnsLabels;
        colTranslators = new HashMap<>();
        for (int i = 0; i < columnsLabels.length; i++) {
            String col = columnsLabels[i];
            Translator translator = translators.get(col);
            if (translator != null) {
                colTranslators.put(i, translator);
            }
        }
    }

    @Override
    public void consume(Object row, int rowIndex) {
        try {
            total = rowIndex;
            onRow(row, rowIndex);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    protected void addRow(List row) {

        List<String> rs = CollectionUtil.map(row, it -> it == null ? null : it.toString());
        cw.writeLine(rs.toArray(new String[0]));

    }

    protected void onRow(Object row, int rowIndex) throws IOException {
        if (rowIndex == 0) {
            if (row instanceof List) {
                isListExport = true;
            } else {
                if (row instanceof Map) {
                    dataIsMap = true;
                }
                //创建转换器和columnsLabels
                init(row.getClass());
            }
            cw.writeHeaderLine(columnsLabels);
        }
        if (isListExport) {
            List li = (List) row;
            if (colTranslators.size() > 0) {
                for (Map.Entry<Integer, Translator> e : colTranslators.entrySet()) {
                    int i = e.getKey();
                    Object v = li.get(i);
                    v = e.getValue().translate(li, columnsLabels[i], v);
                    li.set(i, v);
                }
            }
            addRow(li);
        } else {
            addRow(toRow(row));
        }
    }

    protected void init(Class exportDef) {

        Set<String> fields = new HashSet<>();
        for (Field field : exportDef.getDeclaredFields()) {
            Excel export = field.getAnnotation(Excel.class);
            if (export == null) {
                continue;
            }
            if (!export.out()) {
                fields.add(field.getName());
                continue;
            }
            Item title = new Item();
            title.setKey(field.getName());
            title.setTitle(field.getName());
            if (StringUtil.isNotEmpty(export.value())) {
                title.setTitle(export.value());
            }
            title.setOrder(export.order());
            titles.add(title);
            fields.add(field.getName());
            //使用本身translate,不再额外处理,只处format理格式转换
            if (!translators.containsKey(title.getKey())) {
                Translator translator = null;
                JsonFormat jf = field.getAnnotation(JsonFormat.class);
                if (jf != null && StringUtil.isNotEmpty(jf.pattern())) {
                    translator = new FormatTranslator(jf.pattern());
                    translators.put(title.getKey(), translator);
                }
            }
        }
        //父级,最多处理两级
        for (Field field : exportDef.getSuperclass().getDeclaredFields()) {
            Excel export = field.getAnnotation(Excel.class);
            if (export == null) {
                continue;
            }
            if (!export.out()) {
                continue;
            }
            if (fields.contains(field.getName())) {
                continue;
            }
            Item title = new Item();
            title.setKey(field.getName());
            title.setTitle(field.getName());
            if (StringUtil.isNotEmpty(export.value())) {
                title.setTitle(export.value());
            }
            title.setOrder(export.order());
            titles.add(title);
            if (!translators.containsKey(title.getKey())) {
                Translator translator = null;
                JsonFormat jf = field.getAnnotation(JsonFormat.class);
                if (jf != null && StringUtil.isNotEmpty(jf.pattern())) {
                    translator = new FormatTranslator(jf.pattern());
                    translators.put(title.getKey(), translator);
                }
            }
        }
        Collections.sort(titles);
        columnsLabels = new String[titles.size()];
        for (int i = 0; i < titles.size(); i++) {
            columnsLabels[i] = titles.get(i).getTitle();
        }
    }

    protected List toRow(Object data) {
        List result = new ArrayList();
        for (Item title : titles) {
            Object value = null;
            if (dataIsMap) {
                value = ((Map) data).get(title.getKey());
            } else {
                try {
                    value = BeanUtil.getProperty(data, title.getKey());
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            if (value == null) {
                result.add("");
            } else {
                Translator translator = translators.get(title.getKey());
                if (translator != null) {
                    Object real = translator.translate(data, title.getKey(), value);
                    result.add(real);
                } else {
                    result.add(value);
                }
            }
        }
        return result;
    }

    @Override
    public void end() {
        try {
            cw.flush();
            cw.close();
            out.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            long cost = (System.currentTimeMillis() - startTime) / 1000;
            log.info("导出数据 " + total + " 条，耗时 " + cost + " s ");
        }
    }
}
